package edu.ynu.se.xiecheng.achitectureclass.service;

import edu.ynu.se.xiecheng.achitectureclass.constant.ApiConstants;
import edu.ynu.se.xiecheng.achitectureclass.dto.TokenResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class ApiTokenService {
    private final RestTemplate restTemplate;
    
    private volatile String accessToken;
    private volatile LocalDateTime tokenExpireTime;
    private volatile LocalDateTime lastTokenFetchTime;
    
    private static final int REFRESH_BEFORE_EXPIRY_MINUTES = 5;  // 提前5分钟刷新
    private static final int MIN_TOKEN_FETCH_INTERVAL_MINUTES = 1;  // 最小获取间隔1分钟
    
    @PostConstruct
    public void init() {
        refreshToken();  // 应用启动时获取token
    }
    
    @Scheduled(fixedRate = 60000)  // 每分钟检查一次
    public void scheduleTokenRefresh() {
        try {
            if (shouldRefreshToken()) {
                log.info("计划的token刷新被触发");
                refreshToken();
            }
        } catch (Exception e) {
            log.error("计划的token刷新失败", e);
        }
    }
    
    private synchronized void refreshToken() {
        if (lastTokenFetchTime != null && 
            ChronoUnit.MINUTES.between(lastTokenFetchTime, LocalDateTime.now()) < MIN_TOKEN_FETCH_INTERVAL_MINUTES) {
            log.warn("=== 尝试过于频繁地获取token！跳过刷新 ===");
            return;
        }
        
        try {
            log.warn("=== 获取新token ===");
            TokenResponse response = getToken();
            this.accessToken = response.getAccess_token();
            this.tokenExpireTime = LocalDateTime.now().plusSeconds(response.getExpires_in());
            this.lastTokenFetchTime = LocalDateTime.now();
            
            log.warn("=== 新token获取时间: {}, 将在: {}过期 ===", 
                    lastTokenFetchTime, tokenExpireTime);
        } catch (Exception e) {
            log.error("Token刷新失败", e);
            throw e;
        }
    }
    
    private TokenResponse getToken() {
        try {
            // 设置请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            
            // 设置表单参数
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
            formData.add("scope", ApiConstants.SCOPE);
            formData.add("client_id", ApiConstants.CLIENT_ID);
            formData.add("client_secret", ApiConstants.CLIENT_SECRET);
            formData.add("grant_type", ApiConstants.GRANT_TYPE);
            
            // 创建请求实体
            HttpEntity<MultiValueMap<String, String>> requestEntity = 
                new HttpEntity<>(formData, headers);
            
            log.info("发送token请求到: {}", ApiConstants.TOKEN_URL);
            
            // 发送请求
            ResponseEntity<TokenResponse> response = restTemplate.exchange(
                ApiConstants.TOKEN_URL,
                HttpMethod.POST,
                requestEntity,
                TokenResponse.class
            );
            
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                log.info("成功获取token，有效期: {} 秒", response.getBody().getExpires_in());
                return response.getBody();
            }
            
            throw new RuntimeException("获取访问token失败: 响应体为空");
        } catch (Exception e) {
            log.error("获取访问token失败", e);
            throw new RuntimeException("获取访问token失败: " + e.getMessage());
        }
    }
    
    private boolean shouldRefreshToken() {
        if (accessToken == null || tokenExpireTime == null) {
            return true;
        }
        
        LocalDateTime refreshTime = tokenExpireTime.minusMinutes(REFRESH_BEFORE_EXPIRY_MINUTES);
        return LocalDateTime.now().isAfter(refreshTime);
    }
    
    public String getAccessToken() {
        if (isTokenValid()) {
            long minutesUntilExpiry = ChronoUnit.MINUTES.between(LocalDateTime.now(), tokenExpireTime);
            log.warn("=== Using CACHED token, valid for {} more minutes, expires at: {} ===", 
                    minutesUntilExpiry, tokenExpireTime);
            return accessToken;
        }
        
        synchronized (this) {
            if (isTokenValid()) {
                return accessToken;
            }
            refreshToken();
            return this.accessToken;
        }
    }
    
    private boolean isTokenValid() {
        if (accessToken == null || tokenExpireTime == null) {
            log.debug("Token not initialized yet");
            return false;
        }
        
        LocalDateTime now = LocalDateTime.now();
        boolean valid = now.isBefore(tokenExpireTime.minusMinutes(REFRESH_BEFORE_EXPIRY_MINUTES));
        
        if (!valid) {
            log.debug("Token expired or near expiry at: {}, current time: {}", tokenExpireTime, now);
        }
        
        return valid;
    }
}
